/* decodevob.c
 *
 * Copyright (C) 2000
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>




char vobname[256];

/* read 12:44:01 */
int StringToDaysTime( char * aStr )
{
	int hh,mm,ss, ctime = 0;

	if ( aStr ) {
		if ( strchr( aStr, ':' ) ){
			hh = atoi( aStr );
			mm = atoi( aStr+3 );
			ss = atoi( aStr+6 );
			ctime = (hh*3600) + (mm*60) + ss;
		} else {
			ctime = atoi( aStr );
		}
	}
	return ctime;
}

void TimetoString( long ctime, char *outStr )
{
	long	dd,hh,mm,ss;
	char 	*p;

	ss = ctime % 60;
	mm = (ctime/60) % 60;
	hh = (ctime/3600) % 24;

	p = outStr;
	*p++ = (hh/10) + 0x30;
	*p++ = (hh%10) + 0x30;
	*p++ = ':';
	*p++ = (mm/10) + 0x30;
	*p++ = (mm%10) + 0x30;
	*p++ = ':';
	*p++ = (ss/10) + 0x30;
	*p++ = (ss%10) + 0x30;
	*p++ = 0;
}

#include <sys/timeb.h>
double getms( void )
{
	double  usec1;
	struct timeb t;

	ftime( &t );

	usec1 = t.time + t.millitm/1000.0;
	return usec1;
}




unsigned char inbuf[2048];


#define BLOCK_START_CODE	0x000001ba
#define BLOCK_CRYPT_MASK	0x30000000
#define VID_DETECT_BYTES	0x000001e0
#define AUD_DETECT_BYTES	0x000001c0
#define AC3_DETECT_BYTES	0x000001bd
#define NAV_DETECT_BYTES	0x000001bb
#define PCI_DETECT_BYTES	0x000001bf

/* block[5] specs

  00EE 0000 , DD12 3456 , LLLL LLLL

  EE - Encryption
  DD - DTS Flags,	5bytes
   1 - ESCR flag,	6bytes
   2 - RS_Rate flag,	3 bytes
   3 - DSM Trick mode,	1 byte
   4 - CopyInfo, 	1 byte
   5 - PES_CRC, 	2 bytes
   6 - PES_Ext, 	at least 2 bytes
  LL - Defined length of header

 */


FILE *vobfile, *outm2v=0, *outmpa=0;
FILE *outac3[10] = { 0,0,0,0,0 ,0,0,0,0,0 };
char *outbuffers[10] =  { 0,0,0,0,0 ,0,0,0,0,0 };
long outindex[10] =  { 0,0,0,0,0 ,0,0,0,0,0 };

long out_size = 1000;

long SaveData( FILE *fp, char *data, long len, long id )
{
	long i; unsigned long x=0xffffffff, skip=0;
	char *d;

	if ( !fp ) return 0;

	if ( id == 1 ){		// Vid CHan
		for( i=0;i<len;i++){
			x = (x<<8) | (unsigned char)data[i];
			if ( x == 0x000001BE ){
				skip = data[i+1]<<8 | data[i+2];
				skip += 6;
				break;
			}
		}
	}

//	fwrite( data, len, 1, fp ); return;

	if ( !outbuffers[id] ){
		outbuffers[id] = malloc( (1+out_size)*2048 );
	}
	if ( d = outbuffers[id] ){
		i = outindex[id];
		if ( i >= (out_size*2048) || !data ){
			fwrite( d, i, 1, fp );
			i = outindex[id] = 0;
		}
		if ( i < (out_size*2048) && data ){
			memcpy( d+i, data, len );
			i+=len;
			outindex[id] = i;
		}
	}
	return len;
}

long SaveAc3( FILE *fp, char *data, long len, long chan )
{
	return SaveData( fp, data, len, 2+chan );
}

long Savem2v( FILE *fp, char *data, long len )
{
	return SaveData( fp, data, len, 1 );
}

long Savempa( FILE *fp, char *data, long len )
{
	return SaveData( fp, data, len, 0 );
}


char *preloadbuffer = NULL;
long  preloadpos = 0;
long  preloadread = 0;
long  preloadsize = 1000;

long ReadPacket( char *data, long len )
{
	long doneread;


	if( !preloadbuffer ) {
		preloadbuffer = malloc( 2048 * (preloadsize+1) );
		preloadpos = preloadread = 0;
	}

	if( preloadpos >= preloadread ){
		if ( !vobfile )
			return 0;
		doneread = fread( preloadbuffer, 2048, preloadsize, vobfile );

		if ( feof( vobfile ) ){
			int i = strlen( vobname );
			fclose( vobfile );
			vobfile = NULL;
			while( i>0 ){
				i--;
				if ( isdigit( vobname[i] ) ){
					vobname[i] = vobname[i]+1;
					break;
				}
			}
			if ( i ){
				if ((vobfile = fopen(vobname, "rb")) == 0) {
					printf( "\n" );
					perror( vobname );
					//printf( "remaining data = %d\n", doneread );
					//exit(1);
				} else {
					printf( "\nSpanning to new file %s \n", vobname );
					if ( ! doneread ){
						printf( "Pre-reading data \n", vobname );
						doneread = fread( preloadbuffer, 2048, preloadsize, vobfile );
					}
				}
			}
		}
		preloadpos = 0;
		preloadread = (doneread * 2048);
	}
	//Pre-read 1000 Packets into ram
	if ( data ){
		memcpy( data, preloadbuffer+preloadpos, len );
		preloadpos += len;
		return len;
	} else {
		free( preloadbuffer );
		preloadbuffer = 0;
		preloadpos = preloadread = 0;
		return 0;
	}
}


unsigned long readcode( unsigned char *p )
{
	unsigned long x;

	x  = *p++; x <<= 8;
	x |= *p++; x <<= 8;
	x |= *p++; x <<= 8;
	x |= *p++;

	return x;
}

unsigned short readword( unsigned char *p )
{
	unsigned short x;

	x  = *p++; x <<= 8;
	x |= *p++;

	return x;
}



unsigned long lba=0, lastrlba, nextrlba, lastlba, nextlba, angles = 0;
void parsedvd(int videotrack , int audiotrack)
{
	long	i, tmp,
			code, system_code, header_length, flags,
			substream, packet_length,
			timecode=0, timecodems=0;
	unsigned long *wordbuf = (unsigned long *) inbuf, *p;
	unsigned char *ptr;
	unsigned long outsize = 0;
	double	time1, time2;

	time1 = getms();

	while ( ReadPacket(inbuf, 2048) == 2048) {
		code = readcode( inbuf );
		if ( code != BLOCK_START_CODE )
			continue;
		else
		if ( lba%100 == 0 ){
			double rate1, rate2; char timeStr[32];
			time2 = getms() - time1;
			rate1 = lba*2/time2;
			rate2 = (outsize>>9)/time2;
			TimetoString( time2, timeStr );
			printf( "[%u]: (%0.1fK/s), OUT = %uKb (%0.1fK/s), Time=%s  \r", lba, rate1,   outsize>>10, rate2,  timeStr );
		}

		if ( lba < nextlba && lba > lastlba && angles ){
			lba++;
			continue;
		}

		i = 0x0e;
		system_code = readcode( inbuf+i );		i += 4;
		header_length = readword( inbuf+i );	i += 2;

		switch( system_code ){
			case NAV_DETECT_BYTES:
				i += header_length;
				code = readcode( inbuf+i );		i += 4;
				if ( code == PCI_DETECT_BYTES ){
					packet_length = readword( inbuf+i );	i += 2;
					i += packet_length;
				}
				code = readcode( inbuf+i );		i += 4;
				if ( code == PCI_DETECT_BYTES ){
					packet_length = readword( inbuf+i );	i += 2;
					i += 0x23;
					lastrlba = readcode( inbuf+i );		i += 4;
					nextrlba = readcode( inbuf+i );		i += 4;
					lastlba = lba+lastrlba;
					nextlba = lba+nextrlba;
					if ( lastrlba && nextrlba ){
						printf( "\n[%u]: Angle info. lastlba=%d, nextlba=%d\n", lba, lastrlba, nextrlba );
						angles = 1;
					}
				}
				break;

			case AC3_DETECT_BYTES:
				flags = readword( inbuf+i );			i += 2;
				tmp = inbuf[i++];
				i += tmp;
				substream = inbuf[i++];
				i += 3;
				for( tmp=0; tmp<10; tmp++ ){
					if ( outac3[tmp] && ( substream == 0x80+tmp ) ){
						outsize += SaveAc3( outac3[tmp], &inbuf[i], 2048 - i, tmp );
						break;
					}
				}
				break;
			case VID_DETECT_BYTES:
				flags = readword( inbuf+i );			i += 2;
				tmp = inbuf[i++];
				i += tmp;
				if ( outm2v )
					outsize += Savem2v( outm2v, &inbuf[i], 2048 - i);
				break;
			case AUD_DETECT_BYTES:
				if ( outmpa )
					outsize += Savempa( outmpa, &inbuf[i], 2048 - i);
				break;
		}


		lba++;
	}

	printf( "\nFlushing write buffers\n");
	// flush any write buffers
	for( tmp=0; tmp<10; tmp++ ){
		if ( outac3[tmp] ){
			outsize += SaveAc3( outac3[tmp], NULL, NULL, tmp );
			break;
		}
	}
	if ( outm2v )
		outsize += Savem2v( outm2v, NULL, NULL );

	ReadPacket( 0, 0 );
	printf( "Done\n");

}




int main(int argc, char **argv)
{
	char newname[256], *cp, outdir=0;
	int i,a = -1,v = -1;
	unsigned long t;

	if (argc < 2) {
		fprintf(stderr, "DecodeVOB V1.4\nusage: %s VOBFile [-out X] [-video 0] [-audio 0...8]\n", argv[0]);
		fprintf(stderr, "\t-@ n    - Seek to LBA position\n" );
		fprintf(stderr, "\t-# n    - Seek to Megabyte position (512 LBAs)\n" );
		fprintf(stderr, "\t-out X  - Make output on X drive (C:, D:, E:)\n" );
		fprintf(stderr, "\t-preloadsize - set read ahead buffer size to X LBA units\n" );
		fprintf(stderr, "\t-savesize    - set write buffer size to X LBA units\n" );
		fprintf(stderr, "\t-video X     - set video channel to write (always last param)\n" );
		fprintf(stderr, "\t-audio X     - set audio channel to write (always last param)\n" );
		exit(1);
	}

	strcpy( vobname ,argv[1] );

	if ((vobfile = fopen(vobname, "rb")) == 0) {
		perror(vobname); exit(1);
	}

	for(i=2;i<argc;i++){
		if( strstr( argv[i], "-out" ) ) {
			outdir = *argv[i+1];
		} else
		if( strstr( argv[i], "-@" ) ) {
			t = atoi( argv[i+1] );
			fseek( vobfile, t*2048, SEEK_SET );
			printf( "seeked to new pos %d\n", ftell(vobfile) );
		} else
		if( strstr( argv[i], "-savesize" ) ) {
			t = atoi( argv[i+1] );
			out_size = t;
			printf( "Save size buffer set to %d LBA units (%dKb)\n", t, t*2 );
		} else
		if( strstr( argv[i], "-preloadsize" ) ) {
			t = atoi( argv[i+1] );
			preloadsize = t;
			printf( "Preload size buffer set to %d LBA Units (%dKb)\n", t, t*2 );
		} else
		if( strstr( argv[i], "-#" ) ) {
			t = atoi( argv[i+1] );
			fseek( vobfile, t<<20, SEEK_SET );
			printf( "seeked to new pos %d\n", ftell(vobfile) );
		} else
		if( strstr( argv[i], "-end" ) ) {
			t = atoi( argv[i+1] );
			fseek( vobfile, t<<20, SEEK_END );
			printf( "seeked to new pos %d\n", ftell(vobfile) );
		} else
		if( strstr( argv[i], "-video" ) ) {
			v = atoi( argv[i+1] );
			strcpy( newname, vobname );
			if( outdir ){
				if ( newname[1] == ':' ) newname[0] = outdir;		// replace/fix output DIR
				else sprintf( newname, "%c:\\%s", outdir, vobname );
			}
			if ( cp = strstr( newname, ".vob" ) ){
				sprintf( cp, ".m2v" );
				outm2v = fopen( newname, "wb" );
				if( outm2v )
					printf( "opened %s for writing\n", newname );
				else perror( newname );
			}
		}
		if( strstr( argv[i], "-audio" ) ) {
			a = atoi( argv[i+1] );
			strcpy( newname, vobname );
			if( outdir ){
				if ( newname[1] == ':' ) newname[0] = outdir;		// replace/fix output DIR
				else sprintf( newname, "%c:\\%s", outdir, vobname );
			}

			if ( cp = strstr( newname, ".vob" ) ){
				sprintf( cp, "-stream%d.ac3", a );
				outac3[a] = fopen( newname, "wb" );
				if( outac3[a] )
					printf( "opened %s for writing\n", newname );
				else perror( newname );
			}
		}
	}

	parsedvd( v,a );

	if ( outm2v ) fclose( outm2v );
	for(a=0;a<10;a++)
		if ( outac3[a] ) fclose( outac3[a] );
	if ( vobfile )
		fclose(vobfile);
	exit(0);
}				/* main */
